
#include <everest/odbc/Environment.h>
#include <everest/odbc/SQLException.h>
#include <vector>
#include <string.h>
#include <stdio.h>

using namespace NS_EVEREST::odbc;
using namespace std;

struct Environment::PData
{
	vector<DriverInfo *> vecDriverInfo;
	SQLULEN              idxDriverInfo;


	static bool  loadDriverInfo(SQLHENV hEnv, Environment::PData *pEnvData);  ///< \brief load all driver info in environment driver vector
	static void  clearDriverInfo(Environment::PData *pEnvData);
};

Environment::Environment(): m_EnvHandle(SQL_NULL_HANDLE), m_pData(new PData)
{
	SQLRETURN result = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_EnvHandle);
	SQLPOINTER ver = (SQLPOINTER)SQL_OV_ODBC3;
	result = SQLSetEnvAttr(m_EnvHandle, SQL_ATTR_ODBC_VERSION, ver, 0);
	if ( result == SQL_ERROR )
		throw SQLException(SQL_HANDLE_ENV, m_EnvHandle, "SQLSetEnvAttr", EVEREST_ERRSRC_ARGS);
	m_pData->idxDriverInfo = 0;
}

Environment::~Environment()
{
	if ( this->m_pData ) {
		delete this->m_pData;
		this->m_pData = NULL;
	}

	if ( m_EnvHandle != SQL_NULL_HANDLE) {
		SQLFreeHandle(SQL_HANDLE_ENV, m_EnvHandle);
	}
	m_EnvHandle = SQL_NULL_HANDLE;
}

SQLHENV Environment::getHandle() const
{
	return m_EnvHandle;
}

/**
 * Get ODBC Driver Infomation.
 * @param direction  SQL_FETCH_FIRST for the first DriverInfo, SQL_FETCH_NEXT for the next DriverInfo
 * @return The DriverInfo struct, or NULL if no more driver. The returned pointer should not be deleted by user.
 */
DriverInfo * Environment::getDriverInfo(SQLUSMALLINT direction)
{
	if ( this->m_pData->vecDriverInfo.size() == 0 ) {
		Environment::PData::loadDriverInfo(this->m_EnvHandle, this->m_pData);
	}

	if ( this->m_pData->vecDriverInfo.size() == 0 ) return NULL;  // no driver info loaded

	if ( direction == SQL_FETCH_FIRST ) {   // get first driver info, set the index to 0.
		m_pData->idxDriverInfo = 0;
		return m_pData->vecDriverInfo[m_pData->idxDriverInfo];
	} else {
		++m_pData->idxDriverInfo;
		if (m_pData->idxDriverInfo > m_pData->vecDriverInfo.size()) return NULL;  // NO MORE
		return m_pData->vecDriverInfo[m_pData->idxDriverInfo];
	}
	return NULL;
}

/**
 * @brief Load all ODBC Drivers into Environment object.
 * @param hEnv   Environment handle
 * @param pEnvData  Environment private data
 * @return true
 * @throw SQLException if SQLDrivers failed
 */
bool Environment::PData::loadDriverInfo(SQLHENV hEnv, Environment::PData *pEnvData)
{
	SQLSMALLINT descLen = 0, attrLen = 0;
	SQLSMALLINT descLenTmp = 0, attrLenTmp = 0;
	pEnvData->vecDriverInfo.reserve(64);

	SQLRETURN    result = SQL_SUCCESS;
	SQLUSMALLINT direction = SQL_FETCH_FIRST;
	while ( EVEREST_ODBC_SUCCESS(result) ) {
		result = SQLDrivers(hEnv, direction, NULL, 0, &descLenTmp, NULL, 0, &attrLenTmp);
		if ( result == SQL_NO_DATA) {
			break;
		} else if ( result == SQL_ERROR ) {
			Environment::PData::clearDriverInfo(pEnvData);
			throw SQLException(SQL_HANDLE_ENV, hEnv, "SQLDrivers", EVEREST_ERRSRC_ARGS);
		}
		if ( descLenTmp > descLen ) descLen = descLenTmp;
		if ( attrLenTmp > attrLen ) attrLen = attrLenTmp;
		if ( direction == SQL_FETCH_FIRST) direction = SQL_FETCH_NEXT;
	}
	if ( descLen == 0 && attrLen == 0) return true;  // no driver

	vector<SQLCHAR> descbuf;
	vector<SQLCHAR> attrbuf;
	descbuf.resize(descLen + 1);
	attrbuf.resize(attrLen + 1);

	result = SQL_SUCCESS;
	direction = SQL_FETCH_FIRST;
	while ( EVEREST_ODBC_SUCCESS(result) ) {
		result = SQLDrivers(hEnv, direction, &descbuf[0], descLen, &descLenTmp, &attrbuf[0], attrLen, &attrLenTmp);
		if ( result == SQL_NO_DATA)
			break;
		else if ( result == SQL_ERROR ) {
			Environment::PData::clearDriverInfo(pEnvData);
			throw SQLException(SQL_HANDLE_ENV, hEnv, "SQLDrivers", EVEREST_ERRSRC_ARGS);
		}
		DriverInfo *pDriver = new DriverInfo(&descbuf[0], descLenTmp, &attrbuf[0], attrLenTmp);
		pEnvData->vecDriverInfo.push_back(pDriver);

		if ( direction == SQL_FETCH_FIRST) direction = SQL_FETCH_NEXT;
	}

	return true;
} // loadDriverInfo

/// Clear the driver info list
/// \param pEnvData Private data object of a Environment object.
void Environment::PData::clearDriverInfo(Environment::PData *pEnvData)
{
	vector<DriverInfo *>::iterator iter = pEnvData->vecDriverInfo.begin();
	while( iter != pEnvData->vecDriverInfo.end()) {
		if ( *iter ) delete *iter;
		++iter;
	}
	pEnvData->vecDriverInfo.clear();
	pEnvData->idxDriverInfo = 0;
	return ;
}

BEGIN_NAMESPACE_EVEREST

namespace odbc
{
	Environment  g_globalEnvironment;
}

END_NAMESPACE_EVEREST
